﻿namespace FSChart

open System.Drawing

type DateTime = System.DateTime

type FontDef =
    string * float32 * FontStyle

type LineStyle =
    | Solid
    | Dot
    | Dash
    | DashDot
    | DashDotDot

type LineDef =
    LineStyle * Color * int

type Background =
    | Uniform  of Color
    | Gradient of Color * Color

type Location =
    | Top
    | Bottom
    | Left
    | Right

type Alignment =
    | Near
    | Center
    | Far

type TitleDef =
    { Text        : string
      Location    : Location
      Alignment   : Alignment
      Inside      : bool
      Horizontal  : bool
      Font        : FontDef     option
      Color       : Color       option
      Border      : LineDef     option
      Background  : Background  option }
    
    static member Default =
        { Text = ""; Location = Top; Alignment = Center; Inside = false;
          Horizontal = false; Font = None; Color = None; Border = None; Background = None }
    
    member this.SetText        t = { this with Text        = t      }
    member this.SetLocation    l = { this with Location    = l      }
    member this.SetAlignment   a = { this with Alignment   = a      }
    member this.SetInside      i = { this with Inside      = i      }
    member this.SetHorizontal  h = { this with Horizontal  = h      }
    member this.SetFont        f = { this with Font        = Some f }
    member this.SetColor       c = { this with Color       = Some c }
    member this.SetBorder      b = { this with Border      = Some b }
    member this.SetBackground  b = { this with Background  = Some b }

type LabelDef =
    { AutoFit    : bool
      Angle      : int        option
      Format     : string     option
      Font       : FontDef    option
      Color      : Color      option
      Border     : LineDef    option
      Background : Background option }
    
    static member Default =
        { AutoFit = false; Angle = None; Format = None;
          Font = None; Color = None; Border = None; Background = None }
    
    member this.SetAutoFit    a = { this with AutoFit    = a      }
    member this.SetAngle      a = { this with Angle      = Some a }
    member this.SetFormat     f = { this with Format     = Some f }
    member this.SetFont       f = { this with Font       = Some f }
    member this.SetColor      c = { this with Color      = Some c }
    member this.SetBorder     b = { this with Border     = Some b }
    member this.SetBackground b = { this with Background = Some b }

type GridDef =
    { Pen : LineDef option }
    
    static member Default = { Pen = None }
    member this.SetPen p  = { this with Pen = Some p }

type Scale =
    | Linear of float * bool
    | LogLog of float
    | LogLin of float
    | Range  of float * float * float
    | TimeX  of int

type Crossing =
    | CrossAtMin of bool
    | CrossAtMax of bool
    | CrossAtVal of bool * float

type AxisDef =
    { Title    : TitleDef option
      Labels   : LabelDef option
      Grid     : GridDef  option
      Pen      : LineDef  option
      Scale    : Scale    option
      Crossing : Crossing option
      Ticks    : bool
      Margin   : bool
      Reversed : bool            }
    
    static member Default =
        { Title = None; Labels = None; Grid = None; Pen = None; Scale = None;
          Crossing = None; Ticks = true; Margin = false; Reversed = false }
    
    member this.SetTitle    f = { this with Title    = Some <| f (defaultArg this.Title  TitleDef.Default) }
    member this.SetLabels   f = { this with Labels   = Some <| f (defaultArg this.Labels LabelDef.Default) }
    member this.SetGrid     f = { this with Grid     = Some <| f (defaultArg this.Grid   GridDef .Default) }
    member this.SetPen      p = { this with Pen      = Some p }
    member this.SetScale    s = { this with Scale    = Some s }
    member this.SetCrossing c = { this with Crossing = Some c }
    member this.SetTicks    t = { this with Ticks    = t }
    member this.SetMargin   m = { this with Margin   = m }
    member this.SetReversed r = { this with Reversed = r }

type LegendDef =
    { Location   : Location
      Alignment  : Alignment
      Inside     : bool
      Font       : FontDef    option
      Color      : Color      option
      Border     : LineDef    option
      Background : Background option }
    
    static member Default =
        { Location = Bottom; Alignment = Center; Inside = false
          Font = None; Color = None; Border = None; Background = None }
    
    member this.SetLocation   l = { this with Location   = l      }
    member this.SetAlignment  a = { this with Alignment  = a      }
    member this.SetInside     i = { this with Inside     = i      }
    member this.SetFont       f = { this with Font       = Some f }
    member this.SetColor      c = { this with Color      = Some c }
    member this.SetBorder     b = { this with Border     = Some b }
    member this.SetBackground b = { this with Background = Some b }

type TypeX =
    | XDate
    | XFloat
    | XString

type AreaAlignment =
    | Horizontal of string
    | Vertical   of string

type Style3D =
    { View    : int * int * int
      Depth   : int * int
      Wall    : int
      Realism : bool }
    
    static member Default =
        { View = (0, 0, 0); Depth = (100, 100); Wall = 0; Realism = false }
    
    member this.SetView    v = { this with View    = v }
    member this.SetDepth   d = { this with Depth   = d }
    member this.SetWall    w = { this with Wall    = w }
    member this.SetRealism r = { this with Realism = r }

type AreaDef =
    { Title      : TitleDef      option
      TypeX      : TypeX
      AxisX      : AxisDef
      AxisY      : AxisDef
      AxisY2     : AxisDef       option
      Legend     : LegendDef     option
      Border     : LineDef       option
      Background : Background    option
      AlignTo    : AreaAlignment option
      Style3D    : Style3D       option }
    
    static member Create tx =
        { Title = None; TypeX = tx; AxisX = AxisDef.Default; AxisY = AxisDef.Default; AxisY2 = None;
          Legend = None; Border = None; Background = None; AlignTo = None; Style3D = None }
    
    member this.SetTitle      f = { this with Title   = Some <| f (defaultArg this.Title   TitleDef .Default) }
    member this.SetLegend     f = { this with Legend  = Some <| f (defaultArg this.Legend  LegendDef.Default) }
    member this.SetAxisY2     f = { this with AxisY2  = Some <| f (defaultArg this.AxisY2  AxisDef  .Default) }
    member this.SetStyle3D    f = { this with Style3D = Some <| f (defaultArg this.Style3D Style3D  .Default) }
    
    member this.SetAxisX      f = { this with AxisX  = f this.AxisX }
    member this.SetAxisY      f = { this with AxisY  = f this.AxisY }
    
    member this.SetTypeX      x = { this with TypeX      = x      }
    member this.SetBorder     b = { this with Border     = Some b }
    member this.SetBackground b = { this with Background = Some b }
    member this.SetAlignTo    a = { this with AlignTo    = Some a }

type MarkerShape =
    | Triangle
    | Square
    | Diamond
    | Circle
    | Cross
    | Star

type MarkerDef =
    { Shape : MarkerShape
      Size  : int
      Step  : int
      Color : Color option }
    
    static member Default =
        { Shape = Triangle; Size = 4; Step = 1; Color = None }
    
    member this.SetShape s = { this with Shape = s      }
    member this.SetSize  s = { this with Size  = s      }
    member this.SetStep  s = { this with Step  = s      }
    member this.SetColor c = { this with Color = Some c }

type SeriesType =
    | Line
    | FastLine
    | StepLine
    | Scatter
    | FastPoint
    | Bar         of float
    | BarH        of float
    | StackedBar  of float
    | StackedBarH of float
    | Candlestick of float
    | OHLC
    | FilledArea
    | StackedArea
    | Band
    | Spline      of float
    | SplineArea  of float
    | SplineBand  of float
    | Pie
    | Polar
    | Radar
    | RadarArea
    member this.Cardinality =
        match this with
        | OHLC | Candlestick _ -> 4
        | Band | SplineBand  _ -> 2
        | _ -> 1

type AxisOrder =
    | Primary
    | Secondary

type SeriesDef =
    { SeriesType : SeriesType
      AxisY      : AxisOrder
      Color      : Color     option
      Border     : LineDef   option
      Marker     : MarkerDef option
      Text       : string    option
      Labels     : LabelDef  option }
    
    static member Create t =
        { SeriesType = t; AxisY = Primary; Color = None;
          Border = None; Marker = None; Text = None; Labels = None }
    
    member this.SetMarker f = { this with Marker = Some <| f (defaultArg this.Marker MarkerDef.Default) }
    member this.SetLabels f = { this with Labels = Some <| f (defaultArg this.Labels LabelDef .Default) }
    
    member this.SetType   t = { this with SeriesType = t      }
    member this.SetAxisY  a = { this with AxisY      = a      }
    member this.SetColor  c = { this with Color      = Some c }
    member this.SetPen    p = { this with Border     = Some p }
    member this.SetBorder b = { this with Border     = Some b }
    member this.SetText   t = { this with Text       = Some t }

type DataX =
    | XD of DateTime[]
    | XF of float   []
    | XS of string  []

type SeriesData =
    { DataX  : DataX
      DataY  : float [] list
      Labels : string[] option }
    
    static member DY(d, y) =
        { DataX = XD d; DataY = [y]; Labels = None   }
    
    static member DY(d, ys) =
        { DataX = XD d; DataY = ys ; Labels = None   }
    
    static member XY(x, y) =
        { DataX = XF x; DataY = [y]; Labels = None   }
    
    static member XY(x, ys) =
        { DataX = XF x; DataY = ys ; Labels = None   }
    
    static member XYL(x, y, l) =
        { DataX = XF x; DataY = [y]; Labels = Some l }
    
    static member SY(s, y) =
        { DataX = XS s; DataY = [y]; Labels = None   }
    
    static member SY(s, ys) =
        { DataX = XS s; DataY = ys ; Labels = None   }
    
    static member XF(x, f) =
        { DataX = XF x; DataY = [ Array.map f x ]; Labels = None }
    
    static member XF(x, fs) =
        { DataX = XF x; DataY = [ for f in fs -> Array.map f x ]; Labels = None }
    
    member this.Check(typeX, cardY) =
        let tx, nx =
            match this.DataX with
            | XD d -> XDate  , Array.length d
            | XF f -> XFloat , Array.length f
            | XS s -> XString, Array.length s
        
        if tx <> typeX
            then Some "type mismatch"
        elif nx = 0
            then Some "x vector is empty"
        elif List.length this.DataY <> cardY
            then Some "y cardinality mismatch"
        elif this.DataY |> List.exists (Array.length >> (<>) nx)
            then Some "data vectors have different lengths"
        else
            this.Labels
            |> Option.bind (fun ls ->
                if Array.length ls = nx then None
                else Some "label vector has incorrect length")

type PlotDef = AreaDef * (SeriesDef * SeriesData) list

type ChartContents =
    | Plot   of PlotDef
    | StackV of float list * ChartContents list
    | StackH of float list * ChartContents list
    | Margin of float * float * float * float * ChartContents
    
    member this.Flatten() =
        
        let rec collect (x, y, w, h) acc = function
        | Plot p ->
            (p, (x, y, w, h)) :: acc
        | Margin (l, t, r, b, c) ->
            let dx, dy, dx', dy' = l * w, t * h, r * w, b * h
            c |> collect (x + dx, y + dy, w - dx - dx', h - dy - dy') acc
        | StackV (ds, cs) ->
            let hs = ds |> List.map ((*)(h / List.sum ds))
            let ys = hs |> List.scan (+) y
            Seq.zip3 ys hs cs |> Seq.fold (fun acc (y, h, c) -> collect (x, y, w, h) acc c) acc
        | StackH (ds, cs) ->
            let ws = ds |> List.map ((*)(w / List.sum ds))
            let xs = ws |> List.scan (+) x
            Seq.zip3 xs ws cs |> Seq.fold (fun acc (x, w, c) -> collect (x, y, w, h) acc c) acc
        
        this |> collect (0.0, 0.0, 1.0, 1.0) [] |> List.rev

type ChartDef =
    { Title      : TitleDef   option
      Border     : LineDef    option
      Background : Background option
      Contents   : ChartContents }
    
    static member Create c =
        { Title = None; Border = None; Background = None; Contents = c }
    
    member this.SetTitle      f = { this with Title = Some <| f (defaultArg this.Title TitleDef.Default) }
    
    member this.SetBorder     b = { this with Border     = Some b }
    member this.SetBackground b = { this with Background = Some b }
    member this.SetContents   c = { this with Contents   = c      }

